Expand description
FFI-compatible Future
s
Rust currently doesn’t provide stable ABI nor stable layout of related structs like
dyn Future
or Waker
.
With this crate, we can wrap async blocks or async functions to make a Future
FFI-safe.
FfiFuture
provides the same functionality as Box<dyn Future<Output = T> + Send>
but
it’s FFI-compatible, aka. repr(C)
. Any Future<Output = T> + Send + 'static
can be converted
into FfiFuture
by calling into_ffi
on it, after use
ing the
trait FutureExt
.
FfiFuture
implements Future<Output = T> + Send
. You can await
a FfiFuture
just like
a normal Future
to wait and get the output.
For non-Send
or non-'static
futures, see the section
Variants of FfiFuture
below.
Examples
Provide some async functions in library: (plugin side)
// Compile with `crate-type = ["cdylib"]`.
use async_ffi::{FfiFuture, FutureExt};
#[no_mangle]
pub extern "C" fn work(arg: u32) -> FfiFuture<u32> {
async move {
let ret = do_some_io(arg).await;
do_some_sleep(42).await;
ret
}
.into_ffi()
}
Execute async functions from external library: (host or executor side)
use async_ffi::{FfiFuture, FutureExt};
// #[link(name = "myplugin...")]
extern "C" {
#[no_mangle]
fn work(arg: u32) -> FfiFuture<u32>;
}
async fn run_work(arg: u32) -> u32 {
unsafe { work(arg).await }
}
Proc-macro helpers
If you enable the feature macros
(disabled by default), an attribute-like procedural macro
async_ffi
is available at top-level. See its own documentation for details.
With the macro, the example above can be simplified to:
use async_ffi::async_ffi;
#[no_mangle]
#[async_ffi]
pub async extern "C" fn work(arg: u32) -> u32 {
let ret = do_some_io(arg).await;
do_some_sleep(42).await;
ret
}
Panics
You should know that unwinding across an FFI boundary is Undefined Behaviour.
Panic in Future::poll
Since the body of async fn
is translated to Future::poll
by the compiler, the poll
method is likely to panic. If this happen, the wrapped FfiFuture
will catch unwinding
with std::panic::catch_unwind
, returning FfiPoll::Panicked
to cross the FFI boundary.
And the other side (usually the plugin host) will get this value in the implementation of
<FfiFuture<T> as std::future::Future>::poll
, and explicit propagate the panic,
just like std::sync::Mutex
’s poisoning mechanism.
Panic in Future::drop
or any waker vtable functions Waker::*
Unfortunately, this is very difficult to handle since drop cleanup and Waker
functions are
expected to be infallible. If these functions panic, we would just call std::process::abort
to terminate the whole program.
Variants of FfiFuture
There are a few variants of FfiFuture
. The table below shows their corresponding std
type.
Type | The corresponding std type |
---|---|
FfiFuture<T> | Box<dyn Future<Output = T> + Send + 'static> |
LocalFfiFuture<T> | Box<dyn Future<Output = T> + 'static> |
BorrowingFfiFuture<'a, T> | Box<dyn Future<Output = T> + Send + 'a> |
LocalBorrowingFfiFuture<'a, T> | Box<dyn Future<Output = T> + 'a> |
All of these variants are ABI-compatible to each other, since lifetimes and Send
cannot be
represented by the C ABI. These bounds are only checked in the Rust side. It’s your duty to
guarantee that the Send
and lifetime bounds are respected in the foreign code of your
external fn
s.
Performance and cost
The conversion between FfiFuture
and orinary Future
is not cost-free. Currently
FfiFuture::new
and its alias FutureExt::into_ffi
does one extra allocation.
When poll
ing an FfiFuture
, the Waker
supplied does one extra allocation when clone
d.
It’s recommended to only wrap you async
code once right at the FFI boundary, and use ordinary
Future
everywhere else. It’s usually not a good idea to use FfiFuture
in methods, trait
methods, or generic codes.
abi-stable
support
If you want to use this crate with abi-stable
interfaces. You can enable the feature flag
abi_stable
(disabled by default), then the struct FfiFuture
and friends would derive
abi_stable::StableAbi
.
Structs
- The FFI compatible future type with
Send
bound. - The FFI compatible
std::task::Context
- The FFI compatible future type without
Send
bound. - Represents that the poll function panicked.
Enums
- The FFI compatible
std::task::Poll
Constants
- The ABI version of
FfiFuture
and all variants. Every non-compatible ABI change will increase this number, as well as the crate major version.
Traits
- Helper trait to provide convenience methods for converting a
std::task::Context
toFfiContext
Type Definitions
- The FFI compatible future type with
Send
bound and'static
lifetime, which is needed for most use cases. - The FFI compatible future type without
Send
bound but with'static
lifetime.
Attribute Macros
- async_ffi
macros
A helper macro attribute to converts anasync fn
into a ordinaryfn
returningFfiFuture
.